package rhc.taskSchedule.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

public class HttpUtil {

	private static final String CODE = "UTF-8";

	private static final String CONTENT_TYPE="text/html;charset=utf-8";
	
	/**
	 * 获取连接超时时间
	 */
	protected static final int connectionRequestTimeout = 2000;
	/**
	 * 连接超时时间
	 */
	protected static final int connectTimeout = 5000;
	/**
	 * 获取数据超时时间
	 */
	protected static final int socketTimeout = 30000;

	/**
	 * 重试次数
	 */
	protected static final int retryNum = 2;

	private static final CloseableHttpClient httpClient = createHttpClient();
	private static final Logger logger = Logger.getLogger(HttpUtil.class);

	static{
		SystemCloseHookUtil.addSystemCloseHook(new Runnable(){
			@Override
			public void run() {
				try {
					httpClient.close();
				} catch (Exception e) {
					logger.error("关闭HttpUtil工具类的 httpClient 时出错："+e.getMessage());
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * 发送post请求
	 * 
	 * @param url
	 * @param json
	 * @return
	 */
	public static String doPostContent(String url, String content,String charset,String contentType) {
		HttpRequestBase postMethod = new HttpPost(url);
		String result = null;
		CloseableHttpResponse response = null;
		StringEntity entity = null;
		logger.debug("发起post请求 " + url + ":" + content+":"+contentType);
		entity = new StringEntity(content, StringUtils.isEmpty(charset)?CODE:charset);
		entity.setContentType(StringUtils.isEmpty(contentType)?CONTENT_TYPE:contentType);
		((HttpPost) postMethod).setEntity(entity);
		/* 发送请求，并获取响应对象 */
		result = executeRequest(postMethod);
		logger.debug("post请求 "+url+":"+content+" 请求到数据："+result);
		return result;
	}

	/**
	 * 发送post请求
	 * 
	 * @param url
	 * @param json
	 * @return
	 */
	public static String doGet(String url,Map<String,Object> parameter) {
		url=getGetParameterUrl(url,parameter);
		HttpRequestBase postMethod = new HttpGet(url);
		String result = null;
		/* 发送请求，并获取响应对象 */
		result = executeRequest(postMethod);
		return result;
	}
	
	/**将get参数加入url
	 * @param url
	 * @param parameter
	 * @return
	 */
	public static String getGetParameterUrl(String url,Map<String,Object> parameter){
		NormalUtil.objectsIsNull(url);
		
		if(null != parameter){
			StringBuilder pStrBuilder=new StringBuilder();
			for(String k:parameter.keySet()){
				pStrBuilder.append(k).append("=").append(parameter.get(k).toString()).append("&");
			}
			if(pStrBuilder.length()>0){
				pStrBuilder=pStrBuilder.deleteCharAt(pStrBuilder.length()-1);
				url=url.contains("?")?(url.endsWith("&")?(url+pStrBuilder.toString()):(url+"&"+pStrBuilder.toString())):(url+"?"+pStrBuilder.toString());
			}
		}
		return url;
	}

	/**
	 * post参数
	 * 
	 * @param requestUrl
	 * @param parameters
	 * @return
	 */
	public static String doPostParameter(String requestUrl, Map<String, Object> parameters) {
		HttpRequestBase postMethod = new HttpPost(requestUrl);
		String result = null;
		if (null != parameters) {
			List<org.apache.http.NameValuePair> nvps = new ArrayList<org.apache.http.NameValuePair>(parameters.size());
			Set<String> keys = parameters.keySet();
			Iterator<String> iterator = keys.iterator();
			while (iterator.hasNext()) {
				String key = iterator.next();
				Object value = parameters.get(key);
				if (null == value) {
					continue;
				}
				nvps.add(new BasicNameValuePair(key, value.toString()));
			}
			try {
				((HttpPost) postMethod).setEntity(new UrlEncodedFormEntity(nvps, CODE));
			} catch (UnsupportedEncodingException e) {
				logger.error("创建postparameter时出错：" + requestUrl + ":" + parameters);
				e.printStackTrace();
				return null;
			}
		}
		logger.debug("发起post请求 " + requestUrl + ":" + parameters);
		/* 发送请求，并获取响应对象 */
		result = executeRequest(postMethod);
		return result;
	}
	
	/**获取http文件大小
	 * @param url
	 * @return
	 */
	public static int getHttpFileLength(String url){
		HttpRequestBase request=getGetRequest(url,null,null,null);
		HttpResponse response=null;
		
		try {
			response=executeRequestGetResponse(request);
			String contentLength=response.getFirstHeader("Content-Length").getValue();
			return Integer.valueOf(contentLength);
		} catch (Exception e) {
			logger.error("获取 "+url+" 文件大小出错，"+e.getMessage());
			return 0;
		}finally{
			
			if(null != response){
				try {
					EntityUtils.consume(response.getEntity());
				} catch (IOException e) {
					e.printStackTrace();
				}
				request.releaseConnection();
			}
		}
		
	}

	/**
	 * 获取一个get请求的request
	 * 
	 * @param url
	 *            请求url
	 * @param connectionRequestTimeout
	 *            获取连接超时时间
	 * @param connectTimeout
	 *            连接服务器超时时间
	 * @param socketTimeout
	 *            传输数据超时时间
	 * @return
	 */
	public static HttpRequestBase getGetRequest(String url, Integer connectionRequestTimeout_, Integer connectTimeout_,
			Integer socketTimeout_) {
		HttpGet get=new HttpGet(url);
		RequestConfig config = getConfig(null,
				null == connectionRequestTimeout_ ? connectionRequestTimeout : connectionRequestTimeout_,
				null == connectTimeout_ ? connectTimeout : connectTimeout_,
				null == socketTimeout_ ? socketTimeout : socketTimeout_);
		get.setConfig(config);
		return get;
	}
	
	/**
	 * @param url
	 * @param content
	 * @param charset
	 * @param contentType
	 * @return
	 */
	public static HttpRequestBase getPostContentRequest(String url, String content,String charset,String contentType){
		
		HttpRequestBase postMethod = new HttpPost(url);
		StringEntity entity = null;
		entity = new StringEntity(content, StringUtils.isEmpty(charset)?CODE:charset);
		entity.setContentType(StringUtils.isEmpty(contentType)?CONTENT_TYPE:contentType);
		((HttpPost) postMethod).setEntity(entity);
		
		return postMethod;
	}
	
	/**
	 * @param requestUrl
	 * @param parameters
	 * @return
	 */
	public static HttpRequestBase getPostParameterRequest(String requestUrl, Map<String, Object> parameters) {
		HttpRequestBase postMethod = new HttpPost(requestUrl);
		String result = null;
		if (null != parameters) {
			List<org.apache.http.NameValuePair> nvps = new ArrayList<org.apache.http.NameValuePair>(parameters.size());
			Set<String> keys = parameters.keySet();
			Iterator<String> iterator = keys.iterator();
			while (iterator.hasNext()) {
				String key = iterator.next();
				Object value = parameters.get(key);
				if (null == value) {
					continue;
				}
				nvps.add(new BasicNameValuePair(key, value.toString()));
			}
			try {
				((HttpPost) postMethod).setEntity(new UrlEncodedFormEntity(nvps, CODE));
			} catch (UnsupportedEncodingException e) {
				logger.error("创建postparameter时出错：" + requestUrl + ":" + parameters);
				e.printStackTrace();
				return null;
			}
		}
		return postMethod;
	}
	
	

	/**执行request，获取返回的re's
	 * @param request
	 * @return
	 * @throws Exception 
	 */
	public static HttpResponse executeRequestGetResponse(HttpRequestBase request) throws Exception {
		CloseableHttpResponse response = null;
		Exception throwE=null;
		try {
			response = httpClient.execute(request);
			int statusCode= response.getStatusLine().getStatusCode();
			if (statusCode != org.apache.http.HttpStatus.SC_OK) {
				logger.error("request 返回异常代码："+request.getURI().toString()+","+statusCode);
			}
		} catch (UnsupportedEncodingException e) {
			logger.error("request 请求异常："+request.getURI().toString()+",不支持编码异常，字符编码有问题:" + e.getMessage());
			throwE=e;
		} catch (IOException e) {
			logger.error("request 请求异常："+request.getURI().toString()+",IO异常:" + e.getMessage());
			throwE=e;
		} catch (Exception e) {
			logger.error("request 请求异常："+request.getURI().toString()+",异常：" + e.getMessage());
			throwE=e;
		} finally{
			if(null != throwE){
				throw throwE;
			}
		}
		return response;
	}
	
	/**将输入流里的数据写入输出流
	 * @param input
	 * @param output
	 * @throws IOException 
	 */
	public static void fromInputStreamToOutputStream(InputStream input,OutputStream output) throws IOException{
		
		if(null == input || null == output){
			return;
		}
		byte[] buffer = new byte[1024];
		int r = 0;
		while ((r = input.read(buffer)) != -1) {
			output.write(buffer, 0, r);
		}
	}

	/** 获取请求的配置
	 * @param proxy
	 * @param connectionRequestTimeout
	 * @param connectTimeout
	 * @param socketTimeout
	 * @return
	 */
	public static RequestConfig getConfig(HttpHost proxy, int connectionRequestTimeout, int connectTimeout,
			int socketTimeout) {
		org.apache.http.client.config.RequestConfig.Builder builder = RequestConfig.custom()
				//
				.setAuthenticationEnabled(true)
				//
				.setConnectionRequestTimeout(connectionRequestTimeout)//
				.setConnectTimeout(connectTimeout)// /
				.setSocketTimeout(socketTimeout)//
				.setRedirectsEnabled(true)//
				.setRelativeRedirectsAllowed(true)//
				.setMaxRedirects(5)//
				.setCookieSpec(CookiePolicy.BROWSER_COMPATIBILITY);//
		if (proxy != null) {
			builder.setProxy(proxy);
		}
		return builder.build();
	}

	/**执行一个请求
	 * @param request
	 * @return
	 */
	public static String executeRequest(HttpRequestBase request){
		CloseableHttpResponse response = null;
		String pageContent = null;
		long beginTime=System.currentTimeMillis();
		try {
			response = httpClient.execute(request);
			int statusCode= response.getStatusLine().getStatusCode();
			if (statusCode == HttpStatus.SC_OK) {
				pageContent = toString(response);
				return pageContent;
			}else{
				logger.error("request 返回异常代码："+request.getURI().toString()+","+statusCode);
			}
		} catch (UnsupportedEncodingException e) {
			logger.error("request 请求异常："+request.getURI().toString()+",不支持编码异常，字符编码有问题:" + e.getMessage());
		} catch (IOException e) {
			logger.error("request 请求异常："+request.getURI().toString()+",IO异常:" + e.getMessage());
		} catch (Exception e) {
			logger.error("request 请求异常："+request.getURI().toString()+",异常：" + e.getMessage());
		} finally {
			logger.info(request.toString()+" 耗时："+(System.currentTimeMillis()-beginTime));
			request.releaseConnection();
		}
		return pageContent;
	}

	/**
	 * 创建一个 httpclient
	 * 
	 * @return
	 */
	private static CloseableHttpClient createHttpClient() {

		final int maxPerRoute = 200;
		final int maxTotal = maxPerRoute * 100;

		PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
		connManager.setDefaultMaxPerRoute(maxPerRoute);
		connManager.setMaxTotal(maxTotal);

		SSLContext sslContext = null;
		try {
			sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				// 信任所有
				public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					return true;
				}
			}).build();
		} catch (Exception e) {
			e.printStackTrace();
		}
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
		// 当连接超时或读取超时且没有达到最大重试次数时重试
		HttpRequestRetryHandler myretryhandler = new HttpRequestRetryHandler() {
			@Override
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {

				if ((exception instanceof SocketTimeoutException || exception instanceof ConnectTimeoutException)
						&& executionCount < retryNum) {
					return true;
				}
				return false;
			}
		};
		HttpClientBuilder httpbuilder = HttpClientBuilder.create()//
				.setConnectionManager(connManager)//
				.setDefaultSocketConfig(SocketConfig.custom()//
						.setSoKeepAlive(true)//
						.setSoReuseAddress(true)//
						.build()//
		)//
				.setDefaultRequestConfig(RequestConfig.custom()//
						.setAuthenticationEnabled(true)//
						.setConnectionRequestTimeout(connectionRequestTimeout)//
						.setConnectTimeout(connectTimeout)// /
						.setSocketTimeout(socketTimeout)//
						.setRedirectsEnabled(true)//
						.setRelativeRedirectsAllowed(true)//
						.setMaxRedirects(5)//
						.build()//
		).setSSLSocketFactory(sslsf);
		httpbuilder.setRetryHandler(myretryhandler);
		CloseableHttpClient httpClient = httpbuilder.build();

		return httpClient;
	}

	/**
	 * 将返回的内容转为byte，支持压缩流
	 * 
	 * @param response
	 * @return
	 * @throws IOException
	 */
	protected static byte[] toBytes(HttpResponse response) throws IOException {

		if (response == null) {
			return null;
		}

		AcceptEncodingEnum acceptEncoding = AcceptEncodingEnum.NOTHING;

		Header[] contentEncodings = response.getHeaders("Content-Encoding");

		if (contentEncodings != null && contentEncodings.length != 0) {
			for (Header header : contentEncodings) {
				if (StringUtils.containsIgnoreCase(header.getValue(), "gzip")) {
					acceptEncoding = AcceptEncodingEnum.GZIP;
					break;
				}

				if (StringUtils.containsIgnoreCase(header.getValue(), "deflate")) {
					acceptEncoding = AcceptEncodingEnum.DEFLATE;
					break;
				}
			}
		}

		byte[] bytes = null;
		switch (acceptEncoding) {
		case GZIP:
			bytes = EntityUtils.toByteArray(new GzipDecompressingEntity(response.getEntity()));
			break;
		case DEFLATE:
			bytes = EntityUtils.toByteArray(new DeflateDecompressingEntity(response.getEntity()));
			break;
		default:
			bytes = EntityUtils.toByteArray(response.getEntity());
			break;
		}

		return bytes;
	}

	/**
	 * 获取返回的html，
	 * 
	 * @param response
	 * @param charset
	 * @return
	 * @throws IOException
	 */
	protected static String toString(HttpResponse response) throws IOException {
		return toString(response, "UTF-8");
	}

	/**
	 * 获取返回的html，
	 * 
	 * @param response
	 * @param charset
	 * @return
	 * @throws IOException
	 */
	protected static String toString(HttpResponse response, String charset) throws IOException {

		if (response == null) {
			return null;
		}

		if (StringUtils.isBlank(charset)) {
			charset = "UTF-8";
		}

		byte[] bytes = toBytes(response);

		if (ArrayUtils.isEmpty(bytes)) {
			return null;
		}

		String content = new String(bytes, charset);

		String docCharset = null;

		// 从header中获取编码
		Header[] contentTypes = response.getHeaders("Content-Type");
		if (contentTypes != null && contentTypes.length != 0) {
			for (Header header : contentTypes) {
				if (StringUtils.containsIgnoreCase(header.getValue(), "charset=")) {
					docCharset = StringUtils.substringAfter(header.getValue().toLowerCase(), "charset=");
					docCharset = StringUtils.substringBefore(docCharset, ";");
					docCharset = StringUtils.trimToEmpty(docCharset);
					break;
				}
			}
		}
		// 获取到了编码,没有获取到则默认utf8编码
		if (!(StringUtils.isBlank(docCharset) || docCharset.equalsIgnoreCase(charset))) {
			// 有些文档会出现这个编码，但java中是GB2312，否则会出异常
			if (docCharset.equalsIgnoreCase("GBK2312")) {
				docCharset = "GB2312";
			}
			try {
				content = new String(bytes, docCharset);
			} catch (Exception e) {
				logger.error(e.toString());
			}
		}

		return content;
	}
}

enum AcceptEncodingEnum {
	GZIP, DEFLATE, NOTHING
}
